import SparkMD5 from "spark-md5";
import { Upload } from "./http";

interface FormDataInput {
  file: Blob;
  fileName: string;
}


// 通过文件流获取唯一标识md5
const getMd5ByFile = (file: File, startByte:number, endByte:number) => {
    console.log("file:",file);
  // 通过正则获取文件后缀
  const reg = /\.([a-zA-Z0-9]+)$/.exec(file.name);
  const suffix = reg ? reg[1] : "";

  return new Promise<{ md5: string; suffix: string }>((resolve) => {
    // 读取文件
    //创建一个文件读取器（为浏览器自带API）
    const fileReader = new FileReader();
    const blobSlice = File.prototype.slice;
    const chunkSize = 1024 * 1024; // 1MB chunk size (you can adjust this as needed)
    const end = Math.min(startByte + chunkSize, endByte);
    const blob = blobSlice.call(file, startByte, end);
    
    //调用readAsArrayBuffer函数：传入一个File类型的变量，将文件数据转为二进制数据
    fileReader.readAsArrayBuffer(blob);
    console.log("blob:",blob.size);
    console.log("fileReader:",fileReader);
    //onload是fileReader的事件处理函数（简单理解为钩子函数），会将fileReader处理完事件后，将事件对象用e传入onload中
    fileReader.onload = (e) => {
      console.log("blob:",blob.size);
      //用e获取刚才完成数据转换的二进制数据，并赋值给buffer
      const buffer = e.target?.result as ArrayBuffer;
      //要将二进制数据转换为能被转为MD5的ArrayBuffer格式
      //所以这里需要创建一个SparkMD5.ArrayBuffer对象
      const spark = new SparkMD5.ArrayBuffer();
      console.log("spark:",spark);
      //将刚才的二进制数据传入
      spark.append(buffer);
      
      // 获取文件md5
      const md5 = spark.end();

      //resolve是Promise处理结果的函数，用于将Promise执行完成后将Promise对象从未完成（pending）变为已完成（fulfilled
      //resolve是返回异步操作的结果
      resolve({
        md5,
        suffix,
      });

    };
    fileReader.onerror=(err)=>{
      console.log("blob:",blob.size);
      console.log("读取失败:",err.target!.error!.message);
    }

  });
};


//获取文件是否有传输过，以及获取文件传输到了第几块
const getUploadedChunks = async (params = {}) => {
  /**
   * 在这个函数的参数列表中，有一个默认参数 params = {}。这意味着如果调用 getUploadedChunks 时没有传递任何参数，params 将默认为空对象 {}。

      所以，当您调用 getUploadedChunks({ md5, suffix }) 时，您实际上将一个包含 md5 和 suffix 属性的对象传递给函数，这个对象会被接收为 params，并在函数内部使用。

      这种语法的好处是，您可以轻松地将多个参数打包成一个对象，然后在函数内部进行解构以获取其中的属性，使代码更加简洁和可读。这在处理多个参数时特别有用，可以减少函数签名的复杂性。
   */
  //然后等待这个查询方法返回的数据
  const res = await Upload.getUploadedChunks(params);
  return res;
};

// 获取文件的块数组
const getFileAllChunks = (file: File, md5: string, suffix: string) => {
  let chunkSize = 1 * 1024 * 1024;//这里默认为1MB（1 * 1024 * 1024字节）
  const maxCount = 100;//表示最大的块数量，限制为100块。
  console.log("file.size", file.size);
  //计算预计会有多少块
  let count = Math.ceil(file.size / chunkSize);
  console.log("count>>>", count);


  /**
   * 下面的代码块用于检查块数是否超过了最大允许的块数，
   * 如果超过了，将重新计算 chunkSize 和 count，以确保不超过最大允许的块数
   */
  if (count > maxCount) {
    chunkSize = file.size / maxCount;
    count = maxCount;
  }

  let start = 0;

  const chunks = [];

  
  while (start < count) {
    //调用file自带的切割函数
    const chunk = file.slice(start * chunkSize, (start + 1) * chunkSize);
    //切割完成后添加到chunks
    chunks.push({
      file: chunk,
      fileName: `${md5}_${start + 1}.${suffix}`,
    });

    start++;
  }

  return chunks;
};

//将Chunk中的数据转换为Formdata数据格式
const getFormData = (data: FormDataInput) => {
  const { file, fileName } = data;

  const formData = new FormData();
  formData.append("file", file);
  formData.append("fileName", fileName);

  return formData;
};

//上传块
const uploadChunk = async (formData: FormData) => {
  const res = await Upload.uploadChunk(formData);
  return res;
};

const mergeChunks = async (params = {}) => {
  const res = await Upload.mergeChunks(params);
  return res;
};


//入口函数
// 上传文件改变时函数
const onFileChangeHandler = async (file: File) => {
    console.log("开始文件上传",file.name);
  const title = file.name;
  const size = file.size;
  console.log("size:",size);
  // 获取文件唯一标识
  const startByte = 0; // Start reading from the beginning
  const endByte = Math.min(file.size, 2 * 1024 * 1024); // Read up to 2MB of data
  const { md5, suffix } = await getMd5ByFile(file, startByte, endByte);
  console.log("md5:",md5);

  // 获取文件已经上传过的块
  /**
   * 这行代码实际上是将一个包含两个属性的对象传递给 getUploadedChunks 
   * 函数。这个对象使用了对象解构的语法，其中 { md5, suffix } 实际上是简写形式，
   * 等价于 { md5: md5, suffix: suffix }，它表示创建一个包含 md5 和 suffix 两个属性的对象，
   * 并将这个对象传递给 getUploadedChunks 函数作为参数。
   */
  const res = await getUploadedChunks({ md5, suffix });
    console.log("res:",res);
  //对象解构，将刚才查询已经上传了多少个块返回的数据给解构给code, fileList, filePath, msg
  const { code,fileList,filePath, msg } = res.data;
 
  //满足条件说明已经上传完毕
  if (code === 200 && msg === "uploaded") {
    console.log("已经有了", filePath);
    return res;
  }


  // 给文件按块大小分成块，返回块数组
  const chunks = await getFileAllChunks(file, md5, suffix);

  console.log("chunks>>>", chunks);

  // 并发上传数
  const concurrentChunks = 3;
  const promises = [];


  while (chunks.length) {
      const requests = chunks
      //这一行代码从 chunks 数组中提取前 concurrentChunks 个块，以便并发上传。concurrentChunks 是一个指定的并发上传数，可以一次性上传多个块。
      .splice(0, concurrentChunks)
      //使用 filter 方法，筛选出那些尚未在 fileList 中包含的块。这个过滤操作是为了确保不重复上传已经上传的块。
      .filter((v) => !fileList.includes(v.fileName))
      //使用 map 方法，将块信息转化为 FormData 对象，这是因为上传需要的数据通常以 FormData 的形式发送到服务器。
      .map((chunk) => getFormData(chunk))
      //将转换后的formdata上传
      .map((formData) => uploadChunk(formData));

    promises.push(Promise.all(requests));
  }

  await Promise.all(promises);

  // 请求合并
  const result = await mergeChunks({ md5, suffix,title,size});
  return result.data.success
};

export { onFileChangeHandler };
